/**
*********************************************************************************************************
* @file      lfs_port.c
* @brief     
* @details   
* @author    tang zheng
* @date      2022-08-27
* @version   v0.1
* *********************************************************************************************************
*/


#include "fr30xx.h"
#include "lfs_port.h"

#include "FreeRTOS.h"
#include "task.h"
#include "queue.h"
#include "semphr.h"
#include "user_ipc_st.h"

#define ACCESS_EXTERN_FLASH_WIDTH_32BITS        1

lfs_t lfs;

QueueHandle_t lfs_semaphore;
extern ipc_daily_data_t sync_cm3_data;

volatile bool lfs_flash_read_dma_int = true;

void lfs_flash_read_callback(void)
{
    lfs_flash_read_dma_int = false;
}

void lfs_extern_flash_read(uint8_t *Buffer, uint32_t flash_Address, uint32_t Length)
{
    //DMA
    #if ACCESS_EXTERN_FLASH_WIDTH_32BITS == 0
    lfs_flash_read_dma_int = true;
    IC_W25Qxx_Set_Read_Callback(lfs_flash_read_callback);
    IC_W25Qxx_Read_Quad_Output_DMA(Buffer, (flash_Address & 0x3fffffff), Length);
    while(lfs_flash_read_dma_int)
    {
        vTaskDelay(1);
    }
    #else
    __SPI_DISABLE(spi_flash_handle.SPIx);
    __SPI_DATA_FRAME_SIZE(spi_flash_handle.SPIx, SPI_FRAME_SIZE_32BIT);
    __SPI_RX_ENDIAN_SET(spi_flash_handle.SPIx, TX_RX_Endian_1234);
    while (Length) {
        #define MAX_READ_FRAME_SIZE         (64*4*1)
        uint32_t single_length;
        if (Length >= MAX_READ_FRAME_SIZE) {
            single_length = MAX_READ_FRAME_SIZE;
            uint32_t align_addr =  (uint32_t)Buffer;
			if(align_addr%4)// dma requires address alignment lh add
			{
				single_length = align_addr%4;
				uint32_t value;
                IC_W25Qxx_Read_Quad_Output((void *)&value, (flash_Address & 0x3fffffff), sizeof(uint32_t)>>2);
                memcpy(Buffer, (void *)&value, single_length);
			}else
			{
				lfs_flash_read_dma_int = true;
				IC_W25Qxx_Set_Read_Callback(lfs_flash_read_callback);
	// 			IC_W25Qxx_Read_Quad_Output_DMA(Buffer, (flash_Address & 0x3fffffff), single_length>>2);
	// 			while(lfs_flash_read_dma_int)
	// 			{
	// //                vTaskDelay(1);
	// 			}
                IC_W25Qxx_Read_Quad_Output(Buffer, (flash_Address & 0x3fffffff), single_length>>2);
			}
        }
        else {
			single_length = Length>>2;
			if(single_length)
			{
				 IC_W25Qxx_Read_Quad_Output((void *)Buffer, (flash_Address & 0x3fffffff),single_length);
				 Buffer += single_length<<2;
				 flash_Address += single_length<<2;
			}
			if(Length%4) {
				uint32_t value;
				IC_W25Qxx_Read_Quad_Output((void *)&value, (flash_Address & 0x3fffffff),1); 
				memcpy(Buffer, (void *)&value, (Length%4));
			}
			break;
        }
        Buffer += single_length;
        flash_Address += single_length;
        Length -= single_length;
    }
    __SPI_DISABLE(spi_flash_handle.SPIx);
    __SPI_DATA_FRAME_SIZE(spi_flash_handle.SPIx, SPI_FRAME_SIZE_8BIT);
    __SPI_RX_ENDIAN_SET(spi_flash_handle.SPIx, TX_RX_Endian_4321);
    #endif
}

#if 1
static int lfs_custom_flash_read(const struct lfs_config *c, lfs_block_t block,
            lfs_off_t off, void *buffer, lfs_size_t size)
{

    #ifdef LFS_EXT_FLASH

        //IC_W25Qxx_Read_Data((uint8_t *)buffer, LFS_FLASH_START_ADRRESS+(block * c->block_size) + off, size);
        //IC_W25Qxx_Read_Dual_Output((uint8_t *)buffer, LFS_FLASH_START_ADRRESS+(block * c->block_size) + off, size);

        lfs_extern_flash_read((uint8_t *)buffer, LFS_FLASH_START_ADRRESS+(block * c->block_size) + off, size);
    
    #else

        flash_read(LFS_FLASH_START_ADRRESS+(block * c->block_size)+off,size,(uint8_t *)buffer);

    #endif

    return 0;
}

static int lfs_custom_flash_write(const struct lfs_config *c, lfs_block_t block,
            lfs_off_t off, const void *buffer, lfs_size_t size)
{

    #ifdef LFS_EXT_FLASH

        //IC_W25Qxx_PageProgram((uint8_t *)buffer, LFS_FLASH_START_ADRRESS+(block * c->block_size), size);
    #if ACCESS_EXTERN_FLASH_WIDTH_32BITS == 0
        IC_W25Qxx_PageProgram_Quad((uint8_t *)buffer, LFS_FLASH_START_ADRRESS+(block * c->block_size), size);
    #else

        //IC_W25Qxx_PageProgram((uint8_t *)buffer, LFS_FLASH_START_ADRRESS+(block * c->block_size), size);

        uint32_t flash_Address = LFS_FLASH_START_ADRRESS+(block * c->block_size) + off;

        //printf("w->flash addr:%x size:%d\n",flash_Address,size);

        uint32_t Length = size;
        uint8_t *data =(uint8_t *)buffer;

        uint32_t Page_aligned=0;	   
        Page_aligned=256-(flash_Address%256); 	    
        if(Length<=Page_aligned)
            Page_aligned=Length;
        while(1)
        {	   
            IC_W25Qxx_PageProgram(data,flash_Address,Page_aligned);
            if(Length==Page_aligned)
            {
                break; 
            } 
            else  
            {
                data+=Page_aligned;
                flash_Address+=Page_aligned;	
                Length-=Page_aligned;
                if(Length>256)
                    Page_aligned=256; 
                else 
                    Page_aligned=Length; 	
            }
        }

    #endif

    #else

	    flash_write(LFS_FLASH_START_ADRRESS+(block * c->block_size)+off,size,(uint8_t *)buffer);

    #endif

    return 0;
}


static int lfs_custom_flash_erase(const struct lfs_config *c, lfs_block_t block)
{

    #ifdef LFS_EXT_FLASH

        IC_W25Qxx_EraseSector(LFS_FLASH_START_ADRRESS+(block * c->block_size));

    #else

        flash_erase(LFS_FLASH_START_ADRRESS+(block * c->block_size), HAL_FLASH_BLOCK_4K);

    #endif

    return 0;
}


static int lfs_custom_flash_sync(const struct lfs_config *c)
{
    return 0;
}

int32_t lfs_free_size(void)
{
  int16_t  block;
	
	block=LFS_FLASH_SIZE/HAL_FLASH_BLOCK_4K-lfs_fs_size(&lfs);
	
	if(block >= 0)
	{
        return block*HAL_FLASH_BLOCK_4K;
	}
	else
	{
	   return -1;
	}
}



int lfs_port_lock(const struct lfs_config *c)
{

    uint32_t time_out_counter = 0;

    while(xSemaphoreTake( lfs_semaphore, 0 ) != pdPASS)
    {
        vTaskDelay(1);
        time_out_counter ++;
        if(time_out_counter >= 1000)
            return -1;
    }

    return 0;

    // if( xSemaphoreTake( lfs_semaphore, 0 ) != pdPASS )
    // {
    //     return -1;
    // }
    // else
    // {
    //     return 0;
    // }
}

int lfs_port_unlock(const struct lfs_config *c)
{

    if( xSemaphoreGive( lfs_semaphore ) != pdPASS )
    {
        return -1;
    }
    else
    {
        return 0;
    }
}
#endif

int extern_flash_lock(void)
{

    if( xSemaphoreTake( lfs_semaphore, 0 ) != pdPASS )
    {
        return -1;
    }
    else
    {
        return 0;
    }
}

int extern_flash_unlock(void)
{

    if( xSemaphoreGive( lfs_semaphore ) != pdPASS )
    {
        return -1;
    }
    else
    {
        return 0;
    }
}





void extern_flash_read(uint8_t *Buffer, uint32_t flash_Address, uint32_t Length)
{
    while(extern_flash_lock())
    {
        vTaskDelay(1);
    }

    //IC_W25Qxx_Read_Quad_Output(Buffer, (flash_Address & 0x3fffffff), Length);

//    while (Length) {
//        uint32_t read_length;
//        read_length = Length > 64 ? 64 : Length;
//        IC_W25Qxx_Read_Quad_Output(Buffer, (flash_Address & 0x3fffffff), read_length);
//        Buffer += read_length;
//        flash_Address += read_length;
//        Length -= read_length;
//    }

    //DMA
    #if ACCESS_EXTERN_FLASH_WIDTH_32BITS == 0
    lfs_flash_read_dma_int = true;
    IC_W25Qxx_Set_Read_Callback(lfs_flash_read_callback);
    IC_W25Qxx_Read_Quad_Output_DMA(Buffer, (flash_Address & 0x3fffffff), Length);
    while(lfs_flash_read_dma_int)
    {
        vTaskDelay(1);
    }
    #else

    // fputc('R',NULL);
    __SPI_DISABLE(spi_flash_handle.SPIx);
    __SPI_DATA_FRAME_SIZE(spi_flash_handle.SPIx, SPI_FRAME_SIZE_32BIT);
    __SPI_RX_ENDIAN_SET(spi_flash_handle.SPIx, TX_RX_Endian_1234);
    while (Length) {
        #define MAX_READ_FRAME_SIZE         (64*4*1)
        uint32_t single_length;
        if (Length >= MAX_READ_FRAME_SIZE) {
            single_length = MAX_READ_FRAME_SIZE;
            
            lfs_flash_read_dma_int = true;
            IC_W25Qxx_Set_Read_Callback(lfs_flash_read_callback);
//             IC_W25Qxx_Read_Quad_Output_DMA(Buffer, (flash_Address & 0x3fffffff), single_length>>2);
//             while(lfs_flash_read_dma_int)
//             {
// //                vTaskDelay(1);
//             }
            IC_W25Qxx_Read_Quad_Output(Buffer, (flash_Address & 0x3fffffff), single_length>>2);
        }
        else {
            single_length = Length & (~0x03);
            if (single_length) {
                lfs_flash_read_dma_int = true;
                IC_W25Qxx_Set_Read_Callback(lfs_flash_read_callback);
//                 IC_W25Qxx_Read_Quad_Output_DMA(Buffer, (flash_Address & 0x3fffffff), single_length>>2);
//                 while(lfs_flash_read_dma_int)
//                 {
// //                    vTaskDelay(1);
//                 }
                IC_W25Qxx_Read_Quad_Output(Buffer, (flash_Address & 0x3fffffff), single_length>>2);
                Buffer += single_length;
                flash_Address += single_length;
                Length -= single_length;
            }
            single_length = Length;
            if (single_length) {
                uint32_t value;
                IC_W25Qxx_Read_Quad_Output((void *)&value, (flash_Address & 0x3fffffff), sizeof(uint32_t)>>2);
                memcpy(Buffer, (void *)&value, single_length);
            }
            break;
        }
        
        Buffer += single_length;
        flash_Address += single_length;
        Length -= single_length;
    }
    __SPI_DISABLE(spi_flash_handle.SPIx);
    __SPI_DATA_FRAME_SIZE(spi_flash_handle.SPIx, SPI_FRAME_SIZE_8BIT);
    __SPI_RX_ENDIAN_SET(spi_flash_handle.SPIx, TX_RX_Endian_4321);
    #endif

    extern_flash_unlock();
}

#if 0
void extern_flash_write(uint8_t *Buffer, uint32_t flash_Address, uint32_t Length)
{
    while(extern_flash_lock())
    {
        vTaskDelay(1);
    }
    //IC_W25Qxx_PageProgram_Quad((uint8_t *)Buffer, (flash_Address & 0x3fffffff), Length);
    IC_W25Qxx_PageProgram((uint8_t *)Buffer, (flash_Address & 0x3fffffff), Length);
    extern_flash_unlock();
}
#endif
void extern_flash_write(uint8_t *Buffer, uint32_t flash_Address, uint32_t Length)
{
    while(extern_flash_lock())
    {
        vTaskDelay(1);
    }
    uint32_t Page_aligned=0;	   
	Page_aligned=256-(flash_Address%256); 	    
	if(Length<=Page_aligned)
        Page_aligned=Length;
	while(1)
	{	   
        IC_W25Qxx_PageProgram(Buffer,flash_Address,Page_aligned);
		if(Length==Page_aligned)
        {
            break; 
        } 
	 	else  
		{
			Buffer+=Page_aligned;
			flash_Address+=Page_aligned;	
			Length-=Page_aligned;
			if(Length>256)
                Page_aligned=256; 
			else 
                Page_aligned=Length; 	
		}
        vTaskDelay(1);
	};
    extern_flash_unlock();
}
   
    
void extern_flash_erase(uint32_t flash_Address)
{
    while(extern_flash_lock())
    {
        vTaskDelay(1);
    }

    IC_W25Qxx_EraseSector(flash_Address & 0x3fffffff);

    extern_flash_unlock();
}
void extern_flash_erase_ex(uint32_t flash_Address,uint32_t len)
{
    while(extern_flash_lock())
    {
        vTaskDelay(1);
    }
    for (uint32_t i = 0; i < len; i += 4096)
    {
        IC_W25Qxx_EraseSector((flash_Address+i) & 0x3fffffff);
        vTaskDelay(1);
    }
    extern_flash_unlock();
}

// configuration of the filesystem is provided by this struct
static const struct lfs_config cfg = {
    // block device operations
    .read  = lfs_custom_flash_read,
    .prog  = lfs_custom_flash_write,
    .erase = lfs_custom_flash_erase,
    .sync  = lfs_custom_flash_sync,

    //thread safe
    .lock = lfs_port_lock,
    .unlock = lfs_port_unlock,

    // block device configuration
    .read_size = LFS_RAM_SIZE,
    .prog_size = LFS_RAM_SIZE,
    .block_size     = HAL_FLASH_BLOCK_4K,
    .block_count    = (LFS_FLASH_SIZE/HAL_FLASH_BLOCK_4K),      //1M
    .block_cycles = 100,
    .cache_size = LFS_RAM_SIZE,
    .lookahead_size = LFS_RAM_SIZE,

};


static int lfs_port_traverse_cb(void *data, lfs_block_t block)
{
    return 0;
}

void lfs_larger_test(void)
{
    lfs_file_t file;

    uint32_t SIZE,CHUNKSIZE;
    uint8_t  buffer[256];

    SIZE      =32;
    CHUNKSIZE =31;
	
    // write
    lfs_file_open(&lfs, &file, "cvbcado", LFS_O_RDWR | LFS_O_CREAT);
    printf("lfs_larger_test write %d --\n",lfs_file_size(&lfs, &file));
    for (lfs_size_t i = 0; i < 100; i++) 
    {
       // lfs_size_t chunk = 256;//lfs_min(CHUNKSIZE, SIZE-i);
        for (lfs_size_t b = 0; b < 256; b++) 
		    {
            buffer[b] = i;//rand() & 0xff;
        }
        printf("w %d\n",i);
        lfs_file_write(&lfs, &file, buffer, 256);
    }
    lfs_file_close(&lfs, &file);

	 // read
    lfs_file_open(&lfs, &file, "cvbcado", LFS_O_RDONLY);
    int32_t len=lfs_file_size(&lfs, &file);
    printf("lfs_larger_test read %d\n",len);
    for (lfs_size_t i = 0; i < len/256; i++) 
    {
        lfs_size_t chunk = 256;//lfs_min(CHUNKSIZE, SIZE-i);
        memset(buffer,0x00,sizeof(buffer));
        printf("r %d\n",i);
        lfs_file_read(&lfs, &file, buffer, chunk);
    }
    lfs_file_read(&lfs, &file, buffer, CHUNKSIZE);
    lfs_file_close(&lfs, &file);
}

void lfs_custom_init(void)
{

    //create semaphroe for littlefs lock
   lfs_semaphore = xQueueGenericCreate(( UBaseType_t ) 1,
                                        semSEMAPHORE_QUEUE_ITEM_LENGTH,
                                        queueQUEUE_TYPE_BINARY_SEMAPHORE);

    xSemaphoreGive( lfs_semaphore );
 


   int err = lfs_mount(&lfs, &cfg);
    // reformat if we can't mount the filesystem
    // this should only happen on the first boot
    if (err) 
	{
        lfs_format(&lfs, &cfg);
        err = lfs_mount(&lfs, &cfg);
        printf("lfs mount result:%d\r\n", err);
    }

    if(lfs_fs_traverse(&lfs, lfs_port_traverse_cb, NULL))
    {
        printf("lfs traverse fail \r\n");
        lfs_format(&lfs, &cfg);
        err = lfs_mount(&lfs, &cfg);
    }

//     printf("lfs fs size %d Byte  %d\r\n",lfs_free_size(),lfs_fs_size(&lfs)*4096);
    //lfs_larger_test();
}

// void lfs_custom_format(void)
// {
//     lfs_format(&lfs, &cfg);
// }

#if 0
void lfs_custom_test(void)
{
    lfs_file_t file;

    // read current count
    uint32_t boot_count = 0;
    lfs_file_open(&lfs, &file, "boot_count", LFS_O_RDWR | LFS_O_CREAT);
    lfs_file_read(&lfs, &file, &boot_count, sizeof(boot_count));
    printf("boot_count  %d\r\n",boot_count);
 
    // update boot count
    boot_count += 1;
    lfs_file_rewind(&lfs, &file);
    lfs_file_write(&lfs, &file, &boot_count, sizeof(boot_count));
 
    //printf("lfs fs size %d Byte  %d\r\n",lfs_free_size(),lfs_fs_size(&lfs)*4096);

    // remember the storage is not updated until the file is closed successfully
    lfs_file_close(&lfs, &file);
}



void lfs_custom_test_write_file(void)
{

    lfs_file_t file;

    // read current count
    uint8_t str[] = "abcdefj12321321asdfkjlfalsjdfksajflqwpojdkfjqpwjklsdflq;wfqwefqweqw";
    lfs_file_open(&lfs, &file, "img", LFS_O_RDWR | LFS_O_CREAT);
    lfs_file_write(&lfs, &file, str, sizeof(str));
 
    //printf("lfs fs size %d Byte  %d\r\n",lfs_free_size(),lfs_fs_size(&lfs)*4096);

    // remember the storage is not updated until the file is closed successfully
    lfs_file_close(&lfs, &file);

}
#endif


void lfs_custom_read_sys_param(void)
{
    lfs_file_t file;
    if(!lfs_file_open(&lfs, &file, "sysinfo", LFS_O_RDONLY | LFS_O_CREAT))
    {
        printf("file size %d\n", lfs_file_size(&lfs, &file));
        lfs_file_read(&lfs, &file, &sync_cm3_data, lfs_file_size(&lfs, &file));
    }
    lfs_file_close(&lfs, &file);

}


void lfs_custom_write_sys_param(void)
{
    lfs_file_t file;
    lfs_remove(&lfs,"info");
    if(!lfs_file_open(&lfs, &file, "sysinfo", LFS_O_RDWR))
    {
        lfs_file_write(&lfs, &file, &sync_cm3_data, sizeof(ipc_daily_data_t));
    }
    lfs_file_close(&lfs, &file);

}

